/**
 * 
 * For information on usage and redistribution, and for a DISCLAIMER OF ALL WARRANTIES, see the
 * file, "LICENSE.txt," in this distribution.
 * 
 */

package org.puredata.core;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 
 * Utilities for loading platform-specific libraries using System.loadLibrary, falling back to
 * trying to load from the jar file.
 * 
 * @author Bill Robinson (airbaggins@gmail.com)
 */
public class NativeLoader {

  private static String osName = null;
  private static String osArch = null;


  public static class NativeLibraryLoadError extends UnsatisfiedLinkError {

    private static final long serialVersionUID = 1L;

    public NativeLibraryLoadError(String message) {
      super(message);
    }

    public NativeLibraryLoadError(String message, Throwable cause) {
      // The convenience super-constructor, with cause as second argument, is only available from
      // Java 1.7
      super(message);
      initCause(cause);
    }

  }


  static {
    detectSystem();
  }

  private static String determineOsArch(){
    final String arch = System.getProperty("os.arch").toLowerCase();
    if (arch.indexOf("aarch64") != -1) {
      return "arm64";
    }
    if (arch.indexOf("64") != -1) {
      return "x86_64";
    } 
    if (arch.indexOf("86") != -1) {
      return "x86";
    }
    return arch;
  }

  private static void detectSystem() {

    osArch = determineOsArch();

    osName = System.getProperty("os.name").toLowerCase();

    // Ordered by likeliness to appear in each others' names
    if (osName.indexOf("linux") != -1) {
      osName = "linux";
    } else if (osName.indexOf("windows") != -1) {
      osName = "windows";
    } else if (osName.indexOf("mac") != -1) {
      osName = "mac";
    }
  }

  /**
   * Load the library named, if osNameCheck is the current operating system and osArchCheck is the
   * current architecture.
   * 
   * @param library Name of library to load
   * @param osNameCheck Name of detected operating system (linux/mac/windows)
   * @param osArchCheck Architecture name (x86/x86_64)
   */
  public static void loadLibrary(String library, String osNameCheck, String osArchCheck) {
    if (osArch.equals(osArchCheck)) {
      loadLibrary(library, osNameCheck);
    }
  }

  /**
   * Load the library named, if osNameCheck is the current operating system.
   * 
   * @param library Name of library to load
   * @param osNameCheck Name of detected operating system (linux/mac/windows)
   */
  public static void loadLibrary(String library, String osNameCheck) {
    if (osName.equals(osNameCheck)) {
      loadLibrary(library);
    }
  }

  /** Load the library named.
   *
   * @param library Name of library to load
   */
  public static void loadLibrary(String library) {
    try {
      System.loadLibrary(library);
    } catch (UnsatisfiedLinkError error) {
      loadLibraryFromJar(library);
    }
  }

  /** Try to extract the native library from this Jar file. */
  private static void loadLibraryFromJar(String library) {
    library = System.mapLibraryName(library);
    InputStream in =
        PdBase.class.getResourceAsStream("natives/" + osName + "/" + osArch + "/" + library);
    if (in == null) {
      in = PdBase.class.getResourceAsStream("natives/" + osName + "/" + library);
    }
    if (in == null) {
      throw new NativeLibraryLoadError("Couldn't find " + library + " for this platform " + osName
          + "/" + osArch);
    }
    try {
      File fileOut =
          File.createTempFile(library.replaceFirst("\\.[^.]*$", ""),
              library.replaceFirst("^.*\\.", "."));
      OutputStream out = new FileOutputStream(fileOut);
      byte[] copyBuffer = new byte[1024];
      int amountRead;
      while ((amountRead = in.read(copyBuffer)) != -1) {
        out.write(copyBuffer, 0, amountRead);
      }
      in.close();
      out.close();
      System.load(fileOut.toString());
      fileOut.deleteOnExit();
    } catch (IOException error) {
      throw new NativeLibraryLoadError("Failed to save native library " + library
          + " to temporary file", error);
    }
  }
}
